#include "EDApp.h"

#include <windows.h>

#include "gl.h"
#include "glu.h"
#include "extgl.h"

#ifndef FLT_EPSILON
#define FLT_EPSILON     1.192092896e-07F
#endif

extern HWND ghMainWnd;

void DrawGround(void);
void DrawAxes(float fScale = 1.0f);

// TODO : Implement the smooth skinning algorithm
//
// For this lab, the AnimationSystem classes are already completed.
//
// m_Model contains one mesh, which is the skin vertex data in the bind pose.
// Each vertex in m_Model has a list of weights (m_vWeights). There is a weight for each transform in the hierarchy.
//
// m_BindPose is an animation with one keyframe that is the bind pose. The transform hiearchy of this keyframe
// is made up of EDFrames, instead of simply matrix4fs.
//
// m_Interpolator's m_CurrentKeyFrame tells us what the current interpolated transforms are, just like with
// the articulated animation lab, but uses EDFrames instead of matrix4fs.
//
// m_vSkinVerts is where to store the new vertices from applying the algorithm. It already has the proper size,
// so simply overwrite any existing values in it each frame. You should reset a vertex to (0,0,0) before you start
// adding weighted values to it.
//
// Note: Due to a difference between using row major versus column major matrices, all matrix multiplications of the
//		 smooth skinning algorithm will have to be reversed from how it is presented in the slides ( A*B becomes B*A )
//
void EDApp::ApplySmoothSkinning(void)
{
	vec3f modelSpaceVertex;

	for (unsigned int i = 0; i < vertexNum; ++i)
	{
		unsigned int jointNum = m_Model.GetMeshes()[0].GetWeights()[i].size();
		m_vSkinVerts[i] = vec3f(0.0f, 0.0f, 0.0f);

		for (unsigned int j = 0; j < jointNum; ++j)
		{
			modelSpaceVertex = MatrixVectorMultiply(&inverseBindPoseMatrix[j], &(m_Model.GetMeshes()[0].GetVertices()[i]));

			float weight = m_Model.GetMeshes()[0].GetWeights()[i][j];
			vec3f C = MatrixVectorMultiply(&(m_Interpolator.GetCurrentKeyFrame().m_vTransformFrames[j].GetWorldMat()), &modelSpaceVertex); 

			C *= weight;
			m_vSkinVerts[i] += C;
		}
	}
}

matrix4f EDApp::MatrixInverse(matrix4f const* _mat)
{
	matrix4f inverseMat = matrix4f(_mat->xx, _mat->yx, _mat->zx,0,
								   _mat->xy, _mat->yy, _mat->zy, 0,
								   _mat->xz, _mat->yz, _mat->zz, 0,
								   -dot_product(_mat->axis_x, _mat->axis_w), -dot_product(_mat->axis_y, _mat->axis_w), 
																			-dot_product(_mat->axis_z, _mat->axis_w), 1);

	return inverseMat;
}

vec3f EDApp::MatrixVectorMultiply(matrix4f const* _mat, vec3f const* _vec)
{
	vec3f outVec;
	vec4f paddedVec = vec4f(_vec->x, _vec->y, _vec->z, 1.0f);

	vec4f xAxis = vec4f(_mat->xx, _mat->yx, _mat->zx, _mat->wx);
	vec4f yAxis = vec4f(_mat->xy, _mat->yy, _mat->zy, _mat->wy);
	vec4f zAxis = vec4f(_mat->xz, _mat->yz, _mat->zz, _mat->wz);

	outVec.x = dot_product(xAxis, paddedVec); 
	outVec.y = dot_product(yAxis, paddedVec);
	outVec.z = dot_product(zAxis, paddedVec);

	return outVec;
}

void EDApp::Update(void)
{
	// Get the elapsed seconds since the last frame
	float fTimeElapsed = m_Timer.GetElapsedTime() * 4.0f;
	m_Timer.Reset();

	// Clear our screen
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	m_Interpolator.AddTime( 0.1f );
	m_Interpolator.Process();
	ApplySmoothSkinning();

	// Save the identity
	glPushMatrix();
		
		// Update our camera
		m_Camera.Update(fTimeElapsed);
		m_Camera.ApplyCameraTransform();

		// Set LIGHT0's position
		float fPos[4] = { 0.707f, 0.707f, 0.0f, 0.0f };
		glLightfv( GL_LIGHT0, GL_POSITION, fPos );

		glVertexPointer( 3, GL_FLOAT, 0, m_vSkinVerts[0].v );
		glDisable(GL_LIGHTING);
			glColor3f( 1.0f, 1.0f, 1.0f );
			glDrawElements( GL_TRIANGLES, (GLsizei)m_Model.GetMeshes()[0].GetIndices().size(), GL_UNSIGNED_INT, &m_Model.GetMeshes()[0].GetIndices()[0] );

			glEnable( GL_POLYGON_OFFSET_LINE );
			glPolygonOffset( -1.0f, -1.0f );
			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
			glColor3f( 0.0f, 0.0f, 0.0f );
			glDrawElements( GL_TRIANGLES, (GLsizei)m_Model.GetMeshes()[0].GetIndices().size(), GL_UNSIGNED_INT, &m_Model.GetMeshes()[0].GetIndices()[0] );
			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
			glPolygonOffset( 1.0f, 0.0f );
			glDisable( GL_POLYGON_OFFSET_LINE );
		glEnable(GL_LIGHTING);


		glDisable( GL_DEPTH_TEST );
		glDisable( GL_LIGHTING );
		glLineWidth( 2.0f );
		for( unsigned int i = 0; i < m_Interpolator.GetCurrentKeyFrame().m_vTransformFrames.size(); ++i )
		{
			glPushMatrix();
				glMultMatrixf( m_Interpolator.GetCurrentKeyFrame().m_vTransformFrames[i].GetWorldMat().ma );
				DrawAxes();
			glPopMatrix();
		}
		glLineWidth( 1.0f );
		glEnable( GL_LIGHTING );
		glEnable( GL_DEPTH_TEST );

		// Draw the world's coordinate axes
		glDisable(GL_LIGHTING);
			glDepthMask( 0 );
				glColor3f( 0.0f, 0.5f, 0.0f );
				DrawGround();
			glDepthMask( 1 );
		glEnable(GL_LIGHTING);

	// Restore the identity
	glPopMatrix();
}

void EDApp::Initialize(void)
{
	m_CameraFrame.GetLocalMat().make_identity();
	m_CameraFrame.GetLocalMat().axis_pos.y = 5.0f;
	m_CameraFrame.GetLocalMat().axis_pos.z = 10.0f;
	m_CameraFrame.GetLocalMat().axis_pos.x = 4.0f;

	m_CameraFrame.GetLocalMat().axis_z = vec3f(4.0f, 0.0f, 0.0f) - m_CameraFrame.GetLocalMat().axis_pos;
	m_CameraFrame.GetLocalMat().axis_z.normalize();
	cross_product( m_CameraFrame.GetLocalMat().axis_x, vec3f(0.0f, 1.0f, 0.0f), m_CameraFrame.GetLocalMat().axis_z );
	m_CameraFrame.GetLocalMat().axis_x.normalize();
	cross_product( m_CameraFrame.GetLocalMat().axis_y, m_CameraFrame.GetLocalMat().axis_z, m_CameraFrame.GetLocalMat().axis_x );
	m_CameraFrame.GetLocalMat().axis_y.normalize();

	m_CameraFrame.Update();

	m_Camera.SetFrame( &m_CameraFrame );

	m_Timer.Reset();

	GLfloat noLight[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
	GLfloat fullLight[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

	// Set the color that the screen will be cleared with
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc( GL_LEQUAL );

	// Set up our lighting
	// The last value of one makes this a position, instead of a direction
	GLfloat lightPos[] = {0.0f, 0.0f, 0.0f, 1.0f};
	
	// The light the spot light will point
	GLfloat lightDir[] = {0.0f, 0.0f, -1.0f, 1.0f};
	// Now the three properties of light
	glLightfv(GL_LIGHT0, GL_AMBIENT, noLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, fullLight);
	glLightfv(GL_LIGHT0, GL_SPECULAR, fullLight);
	// Setting the position of the light here will make it and offset from the camera
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

	// Globaly enable lighting
	glEnable(GL_LIGHTING);
	// Enable the one light we are currently using
	glEnable(GL_LIGHT0);
	// Turn off global light
	//glLightModelfv(GL_LIGHT_MODEL_AMBIENT, noLight);	
	// Set up coloring with lighting
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

	// Set up a shiny material
	glMateriali(GL_FRONT, GL_SHININESS, 20);

	glCullFace( GL_BACK );
	glEnable( GL_CULL_FACE );

	glEnableClientState( GL_VERTEX_ARRAY );

	m_Model.LoadMeshesFromFile( "NothingToSeeHere.lol" );
	m_BindPose.LoadFromFile( "ProceduralAnimation.ftw" );
	m_SomeAnimation.LoadFromFile( "DummyAnimation.ftl" );
	m_Interpolator.SetAnimation( &m_SomeAnimation );
	m_vSkinVerts = m_Model.GetMeshes()[0].GetVertices();

	vertexNum = m_Model.GetMeshes()[0].GetVertices().size();

	matrix4f currBindPoseMat;

	// Inverse Bind Pose for 0
	currBindPoseMat = m_BindPose.GetKeyFrames()[0].m_vTransformFrames[0].GetLocalMat();
	inverseBindPoseMatrix[0] = MatrixInverse(&currBindPoseMat);

	// Inverse Bind Pose for 1
	currBindPoseMat = m_BindPose.GetKeyFrames()[0].m_vTransformFrames[1].GetLocalMat();
	inverseBindPoseMatrix[1] = MatrixInverse(&currBindPoseMat);
}

void EDApp::Shutdown(void)
{
}

// Renders a grid
void DrawGround(void)
{
	GLfloat halfSize = 20.0f;
	GLfloat stepSize = 0.5f;
	GLfloat groundLevel = 0.0f;

	glBegin(GL_LINES);
	for(GLfloat x = -halfSize; x <= halfSize; x+= stepSize)
	{
		// These lines horizontal
		glVertex3f(-halfSize, groundLevel, x);
		glVertex3f(halfSize, groundLevel, x);
		// These lines vertical
		glVertex3f(x, groundLevel, -halfSize);
		glVertex3f(x, groundLevel, halfSize);
	}
	glEnd();
}

void DrawAxes(float fScale)
{
	glBegin(GL_LINES);
		glColor3f( 1.0f, 0.0f, 0.0f );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( fScale, 0.0f, 0.0f );

		glColor3f( 0.0f, 1.0f, 0.0f );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( 0.0f, fScale, 0.0f );

		glColor3f( 0.0f, 0.0f, 1.0f );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( 0.0f, 0.0f, fScale );
	glEnd();
}
	